/*
 * Copyright [2022] [https://www.xiaonuo.vip]
 *
 * Snowy采用APACHE LICENSE 2.0开源协议，您在使用过程中，需要注意以下几点：
 *
 * 1.请不要删除和修改根目录下的LICENSE文件。
 * 2.请不要删除和修改Snowy源码头部的版权声明。
 * 3.本项目代码可免费商业使用，商业使用请保留源码和相关描述文件的项目出处，作者声明等。
 * 4.分发源码时候，请注明软件出处 https://www.xiaonuo.vip
 * 5.不可二次分发开源参与同类竞品，如有想法可联系团队xiaonuobase@qq.com商议合作。
 * 6.若您的项目无法满足以上几点，需要更多功能代码，获取Snowy商业授权许可，请在官网购买授权，地址为 https://www.xiaonuo.vip
 */
package vip.xiaonuo.biz.modular.inventorycheck.service.impl;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollStreamUtil;
import cn.hutool.core.util.ObjectUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.google.common.collect.Lists;
import org.apache.commons.lang3.StringUtils;
import org.apache.poi.util.StringUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;
import vip.xiaonuo.auth.core.util.StpClientUtil;
import vip.xiaonuo.biz.modular.cabinetdoor.entity.BizCabinetDoor;
import vip.xiaonuo.biz.modular.cabinetdoor.mapper.BizCabinetDoorMapper;
import vip.xiaonuo.biz.modular.cabinetinfo.entity.BizCabinetInfo;
import vip.xiaonuo.biz.modular.cabinetinfo.mapper.BizCabinetInfoMapper;
import vip.xiaonuo.biz.modular.goods.mapper.BizGoodsMapper;
import vip.xiaonuo.biz.modular.inventorycheck.entity.BizInventoryCheck;
import vip.xiaonuo.biz.modular.inventorycheck.mapper.BizInventoryCheckMapper;
import vip.xiaonuo.biz.modular.inventorycheck.param.*;
import vip.xiaonuo.biz.modular.inventorycheck.service.BizInventoryCheckService;
import vip.xiaonuo.biz.modular.inventorycheck.vo.BizInventoryCheckVO;
import vip.xiaonuo.biz.modular.inventorycheck.vo.ReportExcelExportGoodsVO;
import vip.xiaonuo.biz.modular.inventorycheck.vo.ReportExcelExportReagentVO;
import vip.xiaonuo.biz.modular.inventorycheckreport.entity.BizInventoryCheckReport;
import vip.xiaonuo.biz.modular.inventorycheckreport.mapper.BizInventoryCheckReportMapper;
import vip.xiaonuo.biz.modular.relation.enums.BizRelationCategoryEnum;
import vip.xiaonuo.biz.modular.relation.service.BizRelationService;
import vip.xiaonuo.common.exception.CommonException;

import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 物品扫码盘库Service接口实现类
 *
 * @author Jonathan
 * @date 2023/11/06 09:38
 **/
@Service
public class BizInventoryCheckServiceImpl extends ServiceImpl<BizInventoryCheckMapper, BizInventoryCheck> implements BizInventoryCheckService {
    @Autowired
    private BizGoodsMapper bizGoodsMapper;
    @Autowired
    private BizInventoryCheckReportMapper bizInventoryCheckReportMapper;
    @Autowired
    private BizRelationService bizRelationService;
    @Autowired
    private BizCabinetDoorMapper bizCabinetDoorMapper;
    @Autowired
    private BizCabinetInfoMapper bizCabinetInfoMapper;

    @Override
    public Page<BizInventoryCheckVO> page(BizInventoryCheckPageParam param) {
        return getBaseMapper().page(Page.of(param.getCurrent(), param.getSize()), param);
    }

    @Override
    @Transactional(rollbackFor = Exception.class)
    public void add(BizInventoryCheckAddParam addParam) {
        String cabinetDoorId = addParam.getCabinetDoorId();
        String cabinetInfoId = addParam.getCabinetInfoId();
        String cabinetGroupId;

        // 如果传递了柜门id，查柜门
        if (StringUtils.isNotEmpty(cabinetDoorId)) {
            BizCabinetDoor bizCabinetDoor = bizCabinetDoorMapper.selectById(cabinetDoorId);
            Assert.notNull(bizCabinetDoor, "柜门不存在");
            cabinetInfoId = bizCabinetDoor.getCabinetInfoId();
            cabinetGroupId = bizCabinetDoor.getCabinetGroupId();
        } else { // 如果没有传递柜门id,直接查柜子
            BizCabinetInfo bizCabinetInfo = bizCabinetInfoMapper.selectById(cabinetInfoId);
            Assert.notNull(bizCabinetInfo, "柜子不存在");
            cabinetGroupId = bizCabinetInfo.getCabinetGroupId();
        }
        // 判断权限
        String userId = StpClientUtil.getLoginIdAsString();
        // 判断组柜
        if (StringUtils.isNotEmpty(cabinetGroupId)) {
            boolean isCabinetGroupAdmin = bizRelationService.getRelationTargetIdListByObjectIdAndCategory(cabinetInfoId,
                    BizRelationCategoryEnum.BIZ_RELATION_CABINET_GROUP_AND_ADMIN_USER.getValue())
                    .stream().anyMatch(id -> id.equals(userId));
            if (isCabinetGroupAdmin) {
                createNewInventoryCheck(cabinetGroupId, cabinetDoorId, cabinetInfoId, addParam);
                return;
            }
        }
        // 判断柜子
        if (StringUtils.isNotEmpty(cabinetInfoId)) {
            boolean isCabinetInfoAdmin = bizRelationService.getRelationTargetIdListByObjectIdAndCategory(cabinetInfoId,
                    BizRelationCategoryEnum.BIZ_RELATION_CABINET_INFO_AND_ADMIN_USER.getValue())
                    .stream().anyMatch(id -> id.equals(userId));
            if (isCabinetInfoAdmin) {
                createNewInventoryCheck(cabinetGroupId, cabinetDoorId, cabinetInfoId, addParam);
                return;
            }
        }
        // 判断柜门
        if (StringUtils.isNotEmpty(cabinetDoorId)) {
            boolean isCabinetDoorAdmin = bizRelationService.getRelationTargetIdListByObjectIdAndCategory(cabinetDoorId,
                    BizRelationCategoryEnum.BIZ_RELATION_CABINET_DOOR_AND_ADMIN_USER.getValue())
                    .stream().anyMatch(id -> id.equals(userId));
            if (isCabinetDoorAdmin) {
                createNewInventoryCheck(cabinetGroupId, cabinetDoorId, cabinetInfoId, addParam);
                return;
            }
        }

        throw new CommonException("非柜子或柜门管理员无法进行盘库");
    }

    private void createNewInventoryCheck(String cabinetGroupId, String cabinetDoorId, String cabinetInfoId, BizInventoryCheckAddParam addParam) {
        BizInventoryCheck bizInventoryCheck = BeanUtil.toBean(addParam, BizInventoryCheck.class);
        bizInventoryCheck.setCabinetGroupId(cabinetGroupId);
        bizInventoryCheck.setCabinetInfoId(cabinetInfoId);
        bizInventoryCheck.setCabinetDoorId(cabinetDoorId);
        //如果柜门id不为空，查找柜门中物品的数量
        //否则查找柜子物品中的数量
        long goodsCount = bizGoodsMapper.selectSystemCount(cabinetDoorId, cabinetInfoId);
        bizInventoryCheck.setCurInventory(goodsCount);
        this.save(bizInventoryCheck);
    }

    @Override
    public void edit(BizInventoryCheckEditParam bizInventoryCheckEditParam) {
        BizInventoryCheck bizInventoryCheck = this.queryEntity(bizInventoryCheckEditParam.getId());
        BeanUtil.copyProperties(bizInventoryCheckEditParam, bizInventoryCheck);
        this.updateById(bizInventoryCheck);
    }

    @Transactional(rollbackFor = Exception.class)
    @Override
    public void delete(List<BizInventoryCheckIdParam> bizInventoryCheckIdParamList) {
        // 执行删除
        this.removeBatchByIds(CollStreamUtil.toList(bizInventoryCheckIdParamList, BizInventoryCheckIdParam::getId));
    }

    @Override
    public BizInventoryCheckVO detail(BizInventoryCheckIdParam bizInventoryCheckIdParam) {
        return getBaseMapper().getById(bizInventoryCheckIdParam.getId());
    }

    @Override
    public BizInventoryCheck queryEntity(String id) {
        BizInventoryCheck bizInventoryCheck = this.getById(id);
        if (ObjectUtil.isEmpty(bizInventoryCheck)) {
            throw new CommonException("物品扫码盘库不存在，id值为：{}", id);
        }
        return bizInventoryCheck;
    }

    @Override
    @Transactional
    public void generateReport(BizInventoryCheckIdParam param) {
        BizInventoryCheck bizInventoryCheck = getById(param.getId());
        Assert.notNull(bizInventoryCheck, "无效请求,未找到对应的盘库报告");
        Assert.isTrue(bizInventoryCheck.getState() == 0, "无效请求,盘库报告已生成");
        // 柜子打开空的，也能生成报告
        // long detailCount = bizInventoryCheckDetailMapper.selectCount(new LambdaQueryWrapper<BizInventoryCheckDetail>()
        // .eq(BizInventoryCheckDetail::getInventoryCheckId, param.getId()));
        // Assert.isTrue(detailCount > 0, "盘库记录中没有对应的物品");

        BizInventoryCheck updateBean = new BizInventoryCheck();
        updateBean.setId(param.getId());
        updateBean.setReportUser(StpClientUtil.getLoginIdAsString());
        updateBean.setReportTime(new Date());
        updateBean.setState(1);
        updateById(updateBean);
        // 生成盘库报告明细
        // 1 正常物品列表
        //      1.1 在库物品                         state = 1
        bizInventoryCheckReportMapper.queryInStoreGoods(param.getId()).forEach(item -> {
            item.setState(1);
            item.setInventoryCheckId(param.getId());
            bizInventoryCheckReportMapper.insert(item);
        });
        //      1.2 借出物品                         state = 2
        bizInventoryCheckReportMapper.queryOutStoreGoods(param.getId()).forEach(item -> {
            item.setState(2);
            item.setInventoryCheckId(param.getId());
            bizInventoryCheckReportMapper.insert(item);
        });
        // 2 异常物品列表
        //      2.1 实物在库系统不在库（2件）           state = 3
        bizInventoryCheckReportMapper.queryGoodsInStoreButSystemNotInStore(param.getId()).forEach(item -> {
            item.setState(3);
            item.setInventoryCheckId(param.getId());
            bizInventoryCheckReportMapper.insert(item);
        });
        //      2.2 实物不在库系统在库（3件）           state = 4
        bizInventoryCheckReportMapper.queryGoodsOutStoreButSystemInStore(param.getId()).forEach(item -> {
            item.setState(4);
            item.setInventoryCheckId(param.getId());
            bizInventoryCheckReportMapper.insert(item);
        });
        //      2.3 实物存放处和系统存放处不一致（5件）   state = 5
        bizInventoryCheckReportMapper.queryGoodsStorageIsNotMatched(param.getId()).forEach(item -> {
            item.setState(5);
            item.setInventoryCheckId(param.getId());
            bizInventoryCheckReportMapper.insert(item);
        });
        //      2.4 已报废未出库（1件）                state = 6
        bizInventoryCheckReportMapper.queryGoodsAbandonedNotOut(param.getId()).forEach(item -> {
            item.setState(6);
            item.setInventoryCheckId(param.getId());
            bizInventoryCheckReportMapper.insert(item);
        });
        //      2.5 未扫码入库（1件）                  state = 7
        bizInventoryCheckReportMapper.queryGoodsNotEnterInStore(param.getId()).forEach(item -> {
            item.setState(7);
            item.setInventoryCheckId(param.getId());
            bizInventoryCheckReportMapper.insert(item);
        });
    }

    @Override
    public void export(HttpServletResponse response, BizInventoryExportParam param) {
        param.setState(1); // 报告已生成
        Assert.notEmpty(param.getIds(), "ids不能为空");
        List<BizInventoryCheckVO> reportList = getBaseMapper().list(param);
        Assert.notEmpty(reportList, "未查询到报告");
        List<BizInventoryCheckReport> reportDetails = bizInventoryCheckReportMapper.selectList(new LambdaQueryWrapper<BizInventoryCheckReport>()
                .in(BizInventoryCheckReport::getInventoryCheckId, param.getIds()));
        Assert.notEmpty(reportDetails, "未查询到报告明细");

        try {
            if(param.getGoodsType() == 0) {
                List<ReportExcelExportGoodsVO> dataList =  getGoodsList(reportList,reportDetails);
                ReportExcelExporter.generateExcelForGoods(response, dataList);
            }else {
                List<ReportExcelExportReagentVO> dataList =  getReagentList(reportList,reportDetails);
                ReportExcelExporter.generateExcelForReagent(response, dataList);
            }
        } catch (IOException e) {
            e.printStackTrace();
            throw new CommonException("扫码盘库报告导出失败");
        }
    }

    private List<ReportExcelExportGoodsVO> getGoodsList(List<BizInventoryCheckVO> reportList,List<BizInventoryCheckReport> reportDetails ) {
        return reportList.stream().map(report -> {
            ReportExcelExportGoodsVO vo = new ReportExcelExportGoodsVO();
            BeanUtil.copyProperties(report, vo);
            List<String> cabinetNames = Lists.newArrayList(report.getCabinetGroupName(), report.getCabinetInfoName(), report.getCabinetDoorName());
            vo.setCabinet(cabinetNames.stream().filter(StringUtils::isNotEmpty).collect(Collectors.joining(" ")));
            //在库物品列表
            List<String> inList = getExportDetailList(reportDetails, report, 1);
            vo.setInList(String.join("、", inList));
            //在库物品数（件）
            vo.setInInventory(inList.size());
            //借出物品列表
            List<String> outList = getExportDetailList(reportDetails, report, 2);
            vo.setOutList(String.join("、", outList));
            // 借出物品数（件）
            vo.setOutInventory(outList.size());
            // 正常物品数（件）
            vo.setNormalInventory(inList.size() + outList.size());



            // 实物在库系统不在库列表
            List<String> realInSysOutList = getExportDetailList(reportDetails, report, 3);
            vo.setRealInSysOutList(String.join("、", realInSysOutList));
            // 实物在库系统不在库（件）
            vo.setRealInSysOutInventory(realInSysOutList.size());
            // 实物不在库系统在库列表
            List<String> realOutSysInList = getExportDetailList(reportDetails, report, 4);
            vo.setRealOutSysInList(String.join("、", realOutSysInList));
            // 实物不在库系统在库（件）
            vo.setRealOutSysInInventory(realOutSysInList.size());
            // 实物存放处和系统存放处不一致列表
            List<String> locationNotMatchedList = getExportDetailList(reportDetails, report, 5);
            vo.setLocationNotMatchedList(String.join("、", locationNotMatchedList));
            // 实物存放处和系统存放处不一致（件）
            vo.setLocationNotMatchedInventory(locationNotMatchedList.size());
            // 已报废未出库列表
            List<String> scrappedInList = getExportDetailList(reportDetails, report, 6);
            vo.setScrappedInList(String.join("、", scrappedInList));
            // 已报废未出库（件）
            vo.setScrappedInInventory(scrappedInList.size());
            // 未扫码入库列表
            List<String> notInList = getExportDetailList(reportDetails, report, 7);
            vo.setNotInList(String.join("、", notInList));
            // 未扫码入库（件）
            vo.setNotInInventory(notInList.size());
            // 异常物品数（件）
            vo.setAbnormalInventory(realInSysOutList.size()
                    + realOutSysInList.size()
                    + locationNotMatchedList.size()
                    + scrappedInList.size()
                    + notInList.size());
            return vo;
        }).collect(Collectors.toList());
    }


    private List<ReportExcelExportReagentVO> getReagentList(List<BizInventoryCheckVO> reportList,List<BizInventoryCheckReport> reportDetails ) {
        return reportList.stream().map(report -> {
            ReportExcelExportReagentVO vo = new ReportExcelExportReagentVO();
            BeanUtil.copyProperties(report, vo);
            List<String> cabinetNames = Lists.newArrayList(report.getCabinetGroupName(), report.getCabinetInfoName(), report.getCabinetDoorName());
            vo.setCabinet(cabinetNames.stream().filter(StringUtils::isNotEmpty).collect(Collectors.joining(" ")));
            //在库物品列表
            List<String> inList = getExportDetailList(reportDetails, report, 1);
            vo.setInList(String.join("、", inList));
            //在库物品数（件）
            vo.setInInventory(inList.size());
            //借出物品列表
            List<String> outList = getExportDetailList(reportDetails, report, 2);
            vo.setOutList(String.join("、", outList));
            // 借出物品数（件）
            vo.setOutInventory(outList.size());
            // 正常物品数（件）
            vo.setNormalInventory(inList.size() + outList.size());



            // 实物在库系统不在库列表
            List<String> realInSysOutList = getExportDetailList(reportDetails, report, 3);
            vo.setRealInSysOutList(String.join("、", realInSysOutList));
            // 实物在库系统不在库（件）
            vo.setRealInSysOutInventory(realInSysOutList.size());
            // 实物不在库系统在库列表
            List<String> realOutSysInList = getExportDetailList(reportDetails, report, 4);
            vo.setRealOutSysInList(String.join("、", realOutSysInList));
            // 实物不在库系统在库（件）
            vo.setRealOutSysInInventory(realOutSysInList.size());
            // 实物存放处和系统存放处不一致列表
            List<String> locationNotMatchedList = getExportDetailList(reportDetails, report, 5);
            vo.setLocationNotMatchedList(String.join("、", locationNotMatchedList));
            // 实物存放处和系统存放处不一致（件）
            vo.setLocationNotMatchedInventory(locationNotMatchedList.size());
            // 已报废未出库列表
            List<String> scrappedInList = getExportDetailList(reportDetails, report, 6);
            vo.setScrappedInList(String.join("、", scrappedInList));
            // 已报废未出库（件）
            vo.setScrappedInInventory(scrappedInList.size());
            // 未扫码入库列表
            List<String> notInList = getExportDetailList(reportDetails, report, 7);
            vo.setNotInList(String.join("、", notInList));
            // 未扫码入库（件）
            vo.setNotInInventory(notInList.size());
            // 异常物品数（件）
            vo.setAbnormalInventory(realInSysOutList.size()
                    + realOutSysInList.size()
                    + locationNotMatchedList.size()
                    + scrappedInList.size()
                    + notInList.size());
            return vo;
        }).collect(Collectors.toList());
    }

    public List<String> getExportDetailList(List<BizInventoryCheckReport> reportDetails,
                                            BizInventoryCheckVO report, Integer state) {
        return reportDetails.stream()
                .filter(item -> item.getInventoryCheckId().equals(report.getId()) && item.getState().equals(state))
                .map(item -> {
                    if (report.getGoodsType() == 0) {
                        return String.format("%s(%s)", item.getGoodsName(), item.getGoodsCode());
                    }
                    return String.format("%s(%sg/%sml)", item.getGoodsName(),
                            item.getReagentAvlWeight(), item.getReagentAvlVolume());
                }).collect(Collectors.toList());
    }
}
